Add Rust matrix support for PauliProductRotation#15879
Add Rust matrix support for PauliProductRotation#15879Adithyaphani wants to merge 9 commits intoQiskit:mainfrom
Conversation
|
Thank you for opening a new pull request. Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient. While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone. One or more of the following people are relevant to this code:
|
Cryoris
left a comment
There was a problem hiding this comment.
Thanks for opening a PR on this, @Adithyaphani. I think we can approach this differently to avoid re-implementing the logic to get the matrix for P, which already exists in the around the SparsePauliOp's matrix logic, see e.g.
We should separate the Python and Rust logic here so we can call the matrix construction without requiring the py: Python<'_> token. If you want to outline your approach before implementing it, that could be helpful.
In general: please avoid unnecessary changes in the files or adding unused functions -- it's perfectly fine to use AI but the contribution needs to be up to standard.
|
This is a pre-written response. I believe this may have been LLM generated without attribution. Please confirm the model used, and that the PR was tested and checked by a human before submission, including validating that the PR truly solves the issue at it was described, and takes into account any comments from maintainers in the base issues. |
|
I understand the approach now @Cryoris — instead of re-implementing the Pauli My planned approach:
Does this align with what you had in mind? Happy to outline further |
|
Yeah that sounds right 👍🏻 Also: if you used an LLM to generate the contribution, please attribute this and the model used in the issue description, as Jake pointed out above. |
|
@Cryoris I’ve updated the implementation to reuse the existing matrix construction logic and removed the redundant helper. I have also resolved the merge conflicts with the latest upstream changes. As Jake pointed out regarding the LLM part .For transparency, I used ChatGPT (GPT-5.3) to help refine the approach and better understand the structure. The final implementation, however, was carefully reviewed and adapted manually to ensure alignment with the existing Qiskit codebase. Please let me know if any further changes or improvements are required—I’d be happy to refine it or make modifications further. |
|
Hey @Cryoris , the earlier workflow failures were due to the previous implementation and dependency issues. I’ve updated the code to use a local helper for Pauli matrix construction, resolved the merge conflicts, and addressed those issues. The latest commit now passes all checks. Please let me know if any further refinements are needed. |
|
Thanks @Adithyaphani and @Cryoris. A few quick questions:
|
|
Hello @Cryoris and @jakelishman , I’ve now reworked the implementation to align with the suggested approach -> Fixed lint issues and ensured formatting compliance .Please let me know if any further refinements and modifications are needed. |
|
Hi @alexanderivrii . Regarding the Kronecker product — yes, using ndarray::linalg::kron seems like a good and clean option. It already provides the required functionality, so it helps avoid reimplementing the tensor-product logic and keeps the code simpler and more maintainable. On the matrix size — I agree this is an important consideration. Since the matrix grows exponentially with the number of qubits, constructing dense matrices can quickly become expensive in terms of both memory and computation. My current approach is to stay consistent with how matrix construction is handled elsewhere in Qiskit: only build the dense matrix when it is explicitly required, rather than adding additional layers that might increase overhead. Overall, the goal is to keep the implementation lightweight by reusing the existing Rust-side logic and avoiding unnecessary duplication. Happy to adjust this further if there’s a preferred pattern to follow here. |
ShellyGarion
left a comment
There was a problem hiding this comment.
Thanks for the contribution to qiskit. In addition to the previous comments, can you also add release notes?
|
@ShellyGarion just to clarify, I had pushed an earlier commit before seeing the review comments. I’ve now addressed the feedback in a follow-up commit — including moving the tests to the existing file, aligning with the standard test structure, and updating the parametrization style. The latest state of the PR reflects all the requested changes, and I’m happy to make any further refinements if needed. |
| } | ||
|
|
||
| /// Kronecker (tensor) product of two complex matrices. | ||
| fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> { |
There was a problem hiding this comment.
BTW, did you relate to Alexander's comment on using https://docs.rs/ndarray/latest/ndarray/linalg/fn.kron.html ?
There was a problem hiding this comment.
in addition, note that you should use cargo fmt
There was a problem hiding this comment.
Thanks for pointing that out. I’ve updated the implementation to avoid the custom kron helper and aligned it with the existing ndarray Kronecker-product path as suggested.
Noted — I’ve also run cargo fmt on the updated Rust code in the follow-up commit.
There was a problem hiding this comment.
ok, in addition please run cargo check and cargo clippy as there are some rust errors
There was a problem hiding this comment.
I’ve re-run cargo check and cargo clippy with all targets and features, and addressed the reported issues. The code now builds cleanly without warnings.
Please let me know if you see anything else to improve.
|
Thanks for the note @ShellyGarion . I’ve updated the implementation to avoid the custom kron helper and aligned it with the suggested ndarray Kronecker-product path. I also ran cargo fmt on the updated Rust code in the follow-up commit. Yet any further modifications I'm happy to work on it. |
| Param::Float(f) => f, | ||
| _ => return None, | ||
| }; | ||
| let pauli_mat = gate_matrix::pauli_zx_to_dense_matrix(&self.z, &self.x); |
There was a problem hiding this comment.
You are calling gate_matrix::pauli_zx_to_dense_matrix, but the function you added in sparse_pauli_op.rs is named pauli_zx_to_matrix
There was a problem hiding this comment.
Good catch, thanks for pointing that out.
I’ve updated the call to use pauli_zx_to_matrix and ensured it is correctly defined and used within gate_matrix to avoid cross-crate dependency issues. The naming and usage are now aligned.
Please let me know if anything else needs refinement.
| let identity = Array2::<Complex64>::eye(dim); | ||
| Some( | ||
| identity.mapv(|v| Complex64::new(v.re * cos_val, 0.)) | ||
| + pauli_mat.mapv(|v| Complex64::new(0., -sin_val) * v), | ||
| ) |
There was a problem hiding this comment.
I guess the current identity.mapv(...) tells Rust to:
Iterate over every single one of the 256 elements (even the 240 zeros as it's identity).
Multiply it by cos_val.
Allocate space and write a new matrix with the results.
Instead we can think of doing
// Formula: cos(theta/2)I - i sin(theta/2)P
let mut result = pauli_mat * Complex64::new(0.0, -sin_val);
for i in 0..dim {
result[[i, i]] += cos_val;
}
Some(result)
This may avoid unnecessary memory copies.
There was a problem hiding this comment.
Thanks for the suggestion — that makes sense.
I’ve updated the implementation to construct the result directly from the Pauli matrix and add the cosine term to the diagonal in-place, avoiding the extra allocation from identity.mapv(...). This should reduce unnecessary memory overhead.
Please let me know if this looks good now.
|
Thanks for the review @ShellyGarion with a lot of patience you kept guiding me I resolved the merge conflict in |
|
I'm sorry but this does not seem a high-quality enough PR for Qiskit despite of all of the guidelines of many of the qiskit core team members. I don't know if this is due to the use of LLM tools, but this makes it very difficult for us to continue reviewing this PR.
|
|
@ShellyGarion and @jakelishman , I'm extremely sorry for the mistakes I committed by making modifications in unnecessary files of repository, I goofed up near code parts of multiple files and I immensely appreciate your guidance & patience towards me in helping me to point out mistakes and go ahead in resolving this issue. I totally understand after so many CI checks failures and commits closing this issue is right . But I again raised a new PR which tries to solve the same issue without repeating the same mistakes. I hope you consider this pr .PR :#15927 I request you'll to go through this pr once and I hope this time it passes all CI checks successfully. Looking forward to your responses. |
Fixes #15869
### Summary
Adds Rust-side matrix support for
PauliProductRotation, which was previously available only in the Python layer.This ensures consistent behavior across Python and Rust implementations and enables correct matrix evaluation for Pauli product rotations in the Rust backend.
### Details and comments
This PR introduces the missing matrix functionality for
PauliProductRotationin the Rust circuit layer:Implemented
pauli_product_rotation_matrix(theta, pauli_str)incrates/circuit/src/gate_matrix.rs, computing:exp(-i * θ / 2 * P) = cos(θ / 2) * I - i * sin(θ / 2) * P
using tensor products of single-qubit Pauli operators.
Implemented
Operation::matrix()forPauliProductRotationincrates/circuit/src/operations.rs.Verified that
PackedInstruction::try_matrix()correctly delegatesto the operation matrix implementation in
crates/circuit/src/packed_instruction.rs.Added Python test coverage in
test/python/circuit/test_pauli_product_rotation_matrix.py, including:### Testing
cargo test -p qiskit-circuit python -m pytest test/python/circuit/test_pauli_product_rotation_matrix.py -v